<?php

/**
 * Обертка для файла, загруженного через форму.
 *
 */
class Kernel_Form_Value_File implements Kernel_Form_Value_Interface, Kernel_Form_Value_File_Interface
{

    /**
     * Префикс временных файлов, загружаемых через форму.
     *
     */
    const TMPFILE_PREFIX = 'cms';

    /**
     * Статус файла "временный". Означает, что файл находится в директории
     * для загруженных файлов, перемещать его оттуда надо через
     * move_uploaded_file(), и он будет автоматически удален по окончании работы
     * приложения.
     *
     */
    const UPLOADED = 1;

    /**
     * Статус файла "временный". Означает, что файл был перемещен во временную
     * директорию приложения, и при разрушении объекта его нужно удалить.
     *
     */
    const TEMPORARY = 2;

    /**
     * Статус файла "сохранен". Означает, что файл был перемещен прикладным
     * компонентом, и при разрушении объекта его удалять не нужно.
     *
     */
    const SAVED = 3;

    /**
     * Флаг, указывающий, что файл не был отправлен на сервер через поле с
     * наименованием, указанным в конструкторе.
     *
     * @var boolean
     */
    protected $_absent = false;

    /**
     * Параметры загруженного файла, которые нужно достать из кучи $_FILES.
     *
     * @var array
     */
    static private $_params = array('name', 'type', 'size', 'tmp_name', 'error');

    /**
     * Имя файла у пользователя.
     *
     * @var string
     */
    private $name;

    /**
     * Путь к временному файлу.
     *
     * @var string
     */
    private $tmp_name;

    /**
     * Mime-type файла, полученный от клиента.
     *
     * @var string
     */
    private $type;

    /**
     * Размер файла
     *
     * @var integer
     */
    private $size;

    /**
     * Код ошибки загрузки файла.
     *
     * @var integer
     */
    private $error;

    /**
     * Флаг, указываюций, что файл — временный, и по окончании отправки формы его
     * нужно удалить.
     *
     * @var boolean
     */
    protected $status = self::UPLOADED;

    /**
     * Конструктор.
     *
     * @param   string  $name
     */
    public function __construct($name) 
    {
        foreach (self::$_params as $param) {
            if (!is_null($value = $this->_getFileParam($name, $param))) {
                $this->$param = $value;
            } else {
                $this->_absent = true;
                break;
            }
        }
    }

    /**
     * Возвращает true, если файла нет.
     *
     * @return  boolean
     */
    public function isBlank() 
    {
        return $this->_absent || UPLOAD_ERR_NO_FILE == $this->error;
    }
    
    /**
    * Return whether file is already existed
    * 
    * @return boolean
    */
    public function existed() 
    {
        return false;
    }

    /**
     * Возвращает, был ли файл успешно загружен на сервер.
     *
     * @return  boolean
     */
    public function successful() 
    {
        return !$this->_absent && UPLOAD_ERR_OK == $this->error;
    }

    /**
     * Перемещает файл в новое местоположение.
     *
     * @param   string  $destination
     * @return  boolean
     */
    public function move($destination) 
    {
        if (false !== ($moved = $this->_move($destination))) {
            $this->tmp_name = $destination;
            $this->status = self::SAVED;
        }
        return $moved;
    }

    /**
     * Перемещает файл во временную директорию для последующего восстановления.
     *
     * @return  boolean
     */
    public function temporize() 
    {
        $result = false;
        switch ($this->status) {
            case self::UPLOADED:
                if ($this->move(tempnam(Application::getTmpDir(), self::TMPFILE_PREFIX))) {
                    $this->status = self::TEMPORARY;
                    $result = true;
                }
                break;
            case self::TEMPORARY:
                $result = true;
                break;
        }
        return $result;
    }

    /**
     * Ассоциирует объект с новым файлом — после проведения над ним каких-нибудь
     * манипуляций извне. Например, изменение размеров картинки.
     *
     * @param   string  $path
     */
    public function assign($path) 
    {
        $this->tmp_name = $path;
        $this->status = self::SAVED;
        $this->size = filesize($path);
    }

    /**
     * Возвращает имя файла.
     *
     * @return string
     */
    public function getName() 
    {
        return $this->name;
    }

    /**
     * Возвращает строковое представление значения.
     *
     * @return string
     */
    public function __toString() 
    {
        return basename($this->tmp_name);
    }

    /**
     * Возвращает расширение файла.
     *
     * @return string
     */
    public function getExtension() 
    {
        return get_extension($this->name);
    }

    /**
     * Возвращает путь к файлу.
     *
     * @return string
     */
    public function getPath() 
    {
        return $this->tmp_name;
    }

    /**
     * Возвращает mime-type файла.
     *
     * @return string
     */
    public function getType() 
    {
        return $this->type;
    }

    /**
     * Возвращает размер файла.
     *
     * @return integer
     */
    public function getSize() 
    {
        return $this->size;
    }

    /**
     * Возвращает код ошибки загрузки.
     *
     * @return integer
     */
    public function getError() 
    {
        return $this->error;
    }

    /**
     * Уничтожает объект. Нужно вызывать явным образом, т.к. вызов деструктора
     * по окончании работы приложения не должен инициировать это уничтожение.
     *
     */
    public function destruct() 
    {
        $this->_absent = true;
        if (self::TEMPORARY == $this->status) {
            @unlink($this->tmp_name);
        }
    }

    /**
     * Перемещает файл в новое местоположение.
     *
     * @param   string  $destination
     * @return  boolean
     */
    private function _move($destination) 
    {
        return self::UPLOADED == $this->status
            ? move_uploaded_file($this->tmp_name, $destination)
            : rename($this->tmp_name, $destination);
    }

    /**
     * Возвращает значение указанного параметра файла, загруженного в поле с
     * указанным наименованием. Выглядит по-уебански, зато должно работать с
     * любыми наименованиями полей. А разработчикам PHP за структуру $_FILES
     * - отдельное спасибо :)
     *
     * @param   string  $field
     * @param   string  $param
     * @return  string
     */
    protected function _getFileParam($field, $param) 
    {
        if (false === strpos($field, '[')) {
            $value = @$_FILES[$field][$param];
        } else {
            // оборачиваем имя поля в квадратные скобки
            $index = preg_replace('/^([^[]+)/', '[$1]', $field);
            // добавляем наименование параметра на 1-ю позицию
            $index = preg_replace('/\]\[/', '][' . $param . '][', $index, 1);
            // оборачиваем полученные индексы поля в кавычки
            $index = preg_replace('/\[([^]]+)\]/', '["$1"]', $index);
            // это чтоб Code Analyzer не ругался, а то ведь и вправду криво
            $value = null;
            // а вот вам и собака!
            eval ('$value = @$_FILES' . $index . ';');
        }
        
        return $value;
    }
}
